基本的术语
介绍架构之前,现介绍一下我们现在对于 VS Code 生态中的一些称呼,因为不确定大家是怎么叫的,
架构介绍
electron 与 Web 端的架构本质上大同小异的,他们最大的差别是在于两者的运行时环境,一个是由 electron 包含的 chromium 另一个则是传统的浏览器。 下面我以 Web 端作为例子介绍一下整体架构:
底座本身是一个普通的 Web 应用,但是对于每一个扩展而言,每加载一个扩展底座会通过 webworker 初始化一个插件的运行环境。扩展的 pkg 会在一开始就被主进程的插件服务监听到,拿到扩展的值,此时 pkg 中声明的 command menus view 之类的声明会直接被注册到主进程中的 Registry 中。紧接着,当扩展中的声明的激活时间被触发时,并且此时也满足了其他的条件: 例如是 Web 扩展,vscode 最低版本满足的情况下,扩展的入口文件会被加载。
扩展的 iframe 文件: webWorkerExtensionHostIframe.html 用处:
- 加载扩展的静态资源,例如 js/css 等。
- 通过 web worker 之间的通信,来支持扩展进程与主进程之间的 sdk api 调用
TODO: 双层 iframe 结构。
而如果扩展有自定义 UI 的需求,会在原来 Web Worker 的基础之上,先加载一张 vs/workbench/contrib/webview/browser/pre/index.html iframe(不要问为什么叫 pre,这里我们也不是很清楚,毕竟国内大多数情况下 pre 是作为预发环境的一个代称),在这张 iframe 中,又会加载一张 fake.html 的空 html,此时是不是很奇怪为什么要用这么奇怪的双层 iframe 架构,fake.html 会被扩展中的 Webview html 字符串替换掉,变成真正的 iframe 用来选择自定义 ui 的内容。
双层 iframe 原因:
- 扩展与 Webview 是两个独立的进程。所以需要两张 html 分别来承载。
- fake.html 会被替换成 webview html 的内容。如果没有 pre/index.html 这一层,在 Webview 中可以通过 parent 直接修改底座中的 window 全局变量。这样就会导致扩展可以修改底座的 DOM,这是不安全的。
- 外层的 iframe 是用 service worker 做数据缓存的。 而内层的 fake.html 是用于真正替换 Webview 的内容的。
1. 隔离性
- 底座和扩展之间是互相隔离的。
- 扩展和扩展之间也是互相隔离的。
- 对于 Webview 来说,自然与包含它的扩展来说也是隔离的。
2. 扩展性
VS Code 通过贡献点(英文名叫底座中被称之为 contribution,扩展配置文件中被称之为 contributies),来支持开发者对各式各样的需求,在一个扩展中可以定义的扩展模块有:
- 命令
- 菜单
- 视图
- 配置文件
3. 配置化
- 底座扩展用 worker 隔离。
- 扩展通过 worker 加载,入口文件是扩展的 package.json 以及 package.json 指定的 js entry 文件。
- 对于 Web 版 VS Code 而言加载扩展有很多种方式,实现一套应用市场架构,是最接近 vscode.dev 的; 最简单的加载方式是将扩展的 CDN 入口链接填入 product.json 中的 additionBuiltinExtensions 属性中,默认即可加载。
- 但是需要注意的是,非 Web 扩展不能直接在 Web 版的 VS Code 中运行,会加载不成功,会有一条 warning 提示当前的扩展非 Web 扩展。
- WebView 加载。
- 能够加载 Webview 的位置其实只有 Sidebar 和 Panel. 如果需要定义 Webview,需要在扩展的 Package.json 文件中定义 View 和 ViewContainer
- VS Code 注册 View 和 ViewContainer,并且根据 ViewContainer 的位置,在面板可见时计算出相对的偏移量。
- 再上头盖一张 Webview 绝对定位。
- 扩展与底座之间底层是通过 rpc 字符通信的,所以本质上只能传递字符串,对象(序列化、反序列化之后会丢属性)因此所有的 Provider 都是在扩展中继承 VS Code 的类型实现,实例中声明一个函数,返回一个数组、对象等。
进程
有几个主要进程:
- 主进程,负责管理其他进程。
- 渲染进程。一个或者多个。
- 扩展进程。基本上是多个, remoteExtensionHost, webExtensionHost
进程之前通过 rpc 进行通信 (electron 是直接通过 rpc 通信的;开天的 Web 端与后端是通过 WS 通信;),通过返回一个 Proxy 将底层 rpc 进行封装用于远程调用。
electron 以及 sandbox 架构的升级
sandbox 主要指的是 electron 的 sandbox 模式,现在应该已经处于默认开启阶段,但是也可以关闭。
SDK API 的实现
扩展进程通过 RPC 调用主进程的服务实现。 Web 架构不是 RPC, 而是 Worker 的通信机制。
ExtensionHost 服务提供了一个 SDK (一个对象)环境。
通信
- electron
如何实现多端构建
- 架构分离,氛围 web/electron 端,有各自的构建脚本和入口。
- 使用依赖注入的方式实现。
VS Code 中的 worker 使用
两个都有使用到。
- Web Worker: 用于实现扩展进程,与主进程隔离。
- Service Worker: 通信? 需要再看一下
- 要求 https 的第一个原因
VS Code 这样的架构有什么好处?
- 安全。隔离性。
- 底座与插件隔离:确保扩展(Worker 访问不到底座的 DOM,也无法访问底座的数据,只能通过 RPC 通信)
- 但是 Web 版的架构与 electron 的架构有些差别,electron 端通过 rpc 进行通信,并且在渲染进程中原本可以直接访问 Node 进程中的代码,这是不太安全的。于是 VS Code 花了 2 年时间将 electron 的架构改造成了现在的架构,开启 electron 端的 sandbox 架构:渲染进程只能通过 rpc 访问 Node 的代码(通过暴露的函数,而非直接通过 require fs 等模块)。
- 插件隔离:某一个扩展挂掉不会影响其他扩展,也不会影响底座的正常运行。
- UI 统一。可以自定义的部分,例如主题、icon 等,扩展几乎没有权限自定义 UI。
架构设计?
- 为什么不能使用 react 组件(可以作为一个难点)
- 如何将 React 融合进 VS Code 中
- VS Code Web 的部署架构
Web 与 Worker 之间的通信方式?
性能优化
- 最大的性能问题是 Webview. 简单的页面,通过 Webpack 插件将 js/css 都内联进 html 中。但是对复杂的页面,还是需要通过 CDN 插入。
安全
- 监控:监控的指标计算
业务如何接入?
- 应用同一份底座,通过指定不同的 product.json 和 web-configraution.json 指定加载不同的 additionBuiltinExtension 初始化不同默认的扩展。
Command 有没有规范?
国际化?
如何设计接口? 新增 api
electron 架构
chromium + Node.js + Native API = electron
- 使用 Web 技术来编写 UI,用 chrome 浏览器内核来运行
- 使用 NodeJS 来操作文件系统和发起网络请求
- 使用 NodeJS C++ Addon 去调用操作系统的 native API
应用架构
- 1 个主进程:一个 Electron App 只会启动一个主进程,它会运行 package.json 的 main 字段指定的脚本
- N 个渲染进程:主进程代码可以调用 Chromium API 创建任意多个 web 页面,而 Chromium 本身是多进程架构,每个 web 页面都运行在属于它自己的渲染进程中
进程间通讯:
- Render 进程之间的通讯本质上和多个 Web 页面之间通讯没有差别,可以使用各种浏览器能力如 localStorage,本质上并不能够直接通信。
- Render 进程与 Main 进程之间也可以通过 API 互相通讯 (ipcRenderer/ipcMain)
VSCode 技术架构
多进程架构
- 主进程:VSCode 的入口进程,负责一些类似窗口管理、进程间通信、自动更新等全局任务
- 渲染进程:负责一个 Web 页面的渲染,这里会存在多个。
- 插件宿主进程:每个插件的代码都会运行在一个独属于自己的 NodeJS 环境的宿主进程中,插件不允许访问 UI
- Debug 进程:Debugger 相比普通插件做了特殊化
- Search 进程:搜索是一类计算密集型的任务,单开进程保证软件整体体验与性能